package com.gitee.wsl.io.channel.read.ext

import com.gitee.wsl.io.channel.ByteChannel
import com.gitee.wsl.io.channel.read.ByteReadChannel
import com.gitee.wsl.io.channel.write.ByteWriteChannel
import com.gitee.wsl.io.channel.ChannelJob
import com.gitee.wsl.io.channel.write.ext.close
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext


class ReaderScope(
    val channel: ByteReadChannel,
    override val coroutineContext: CoroutineContext
) : CoroutineScope

class ReaderJob internal constructor(
    val channel: ByteWriteChannel,
    override val job: Job
) : ChannelJob

@Suppress("UNUSED_PARAMETER")
fun CoroutineScope.reader(
    coroutineContext: CoroutineContext = EmptyCoroutineContext,
    autoFlush: Boolean = false,
    block: suspend ReaderScope.() -> Unit
): ReaderJob = reader(coroutineContext, ByteChannel(), block)

@OptIn(InternalCoroutinesApi::class)
fun CoroutineScope.reader(
    coroutineContext: CoroutineContext,
    channel: ByteChannel,
    block: suspend ReaderScope.() -> Unit
): ReaderJob {
    val job = launch(coroutineContext) {
        val nested = Job(this.coroutineContext.job)
        try {
            block(ReaderScope(channel, this.coroutineContext + nested))
            nested.complete()

            if (this.coroutineContext.job.isCancelled) {
                channel.cancel(this.coroutineContext.job.getCancellationException())
            }
        } catch (cause: Throwable) {
            nested.cancel("Exception thrown while reading from channel", cause)
            channel.close(cause)
        } finally {
            nested.join()
            runCatching { channel.flushAndClose() }
        }
    }.apply {
        invokeOnCompletion {
            if (it != null && !channel.isClosedForRead) {
                channel.cancel(it)
            }
        }
    }

    return ReaderJob(channel, job)
}
